package hydrap2p.speedlimiter;

import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Timer;
import java.util.TreeMap;
import java.util.Map.Entry;

import hydrap2p.download.Source;
import hydrap2p.logging.Level;

public abstract class SpeedLimiterLogic {
	private static final int UPDATEDELAY = 200;	// number of milliseconds between updates
	protected final Map<String, SpeedLimitedQueue> queues;
	private final List<Timer> tickers;
	private boolean running;
	
	public SpeedLimiterLogic()
	{
		running=true;
		tickers=new LinkedList<Timer>();
		queues = Collections.synchronizedMap(new TreeMap<String, SpeedLimitedQueue>());
		
		Configuration configs = new Configuration();
		configs.loadConfigs();
		createQueues(configs);
		scheduleTickers();
	}
	
	/**
	 * Populate queues instance field
	 * Only called during construction
	 */
	protected abstract void createQueues(Configuration configs);
	
	/**
	 * Initializes the update timers
	 * Only called during construction
	 */
	private void scheduleTickers()
	{
		for (SpeedLimitedQueue queue:queues.values())
		{
			Timer temp=new Timer();
			temp.scheduleAtFixedRate(new UpdateTicker(queue), UPDATEDELAY, UPDATEDELAY);
			tickers.add(temp);
		}
	}
	
	public void shutdown()
	{
		synchronized(tickers)
		{
			running=false;
			for (Timer temp:tickers)
				temp.cancel();
		}
	}
	
	/**
	 * Enqueue this transfer in the correct queue
	 * @param peer
	 * @param download
	 * @param amount
	 */
	public void attempt(Source peer, boolean download, int amount)
	{
		if (running)
		{
			if (download)
				SpeedLimiterLogging.log(Level.VERBOSE, "Speed Limiter - Queue", "Added new queue request: "+"("+peer.getProtocol()+") "+peer.getIp()+":"+peer.getPort()+" wants to download "+amount+" bytes");
			else
				SpeedLimiterLogging.log(Level.VERBOSE, "Speed Limiter - Queue", "Added new queue request: "+"("+peer.getProtocol()+") "+peer.getIp()+":"+peer.getPort()+" wants to upload "+amount+" bytes");
			
			String queue=selectQueue(peer, download, amount);
			queues.get(queue).attempt(peer, download, amount);
			
			if (download)
				SpeedLimiterLogging.log(Level.VERBOSE, "Speed Limiter - Queue", "Finished queue request: "+"("+peer.getProtocol()+") "+peer.getIp()+":"+peer.getPort()+" wants to download "+amount+" bytes");
			else
				SpeedLimiterLogging.log(Level.VERBOSE, "Speed Limiter - Queue", "Finished queue request: "+"("+peer.getProtocol()+") "+peer.getIp()+":"+peer.getPort()+" wants to upload "+amount+" bytes");
		}
	}
	
	/**
	 * Decide which queue the given transfer should go to
	 * Only returns the name, not the actual queue
	 * @param peer
	 * @param download
	 * @param amount
	 * @return
	 */
	protected abstract String selectQueue(Source peer, boolean download, int amount);
	
	public Map<String, Integer> getQueues()
	{
		synchronized(queues)
		{
			Map<String, Integer> ret=new TreeMap<String, Integer>();
			for (Entry<String, SpeedLimitedQueue> queueinfo:queues.entrySet())
			{
				ret.put(queueinfo.getKey(),queueinfo.getValue().getSpeed());
			}
			return ret;
		}
	}
	public void setQueueLimit(String queuename, int speed) throws InvalidParameter
	{
		if (speed<0)
			throw new InvalidParameter(queuename, speed);
		
		if (queues.containsKey(queuename))
			queues.get(queuename).setSpeed(speed);
		else
			throw new InvalidParameter(queuename, speed);

		Configuration configs = new Configuration();
		configs.saveQueue(queuename, speed);
		
		SpeedLimiterLogging.log(Level.INFO, "Speed Limiter - Set Limit", "Set limit on queue "+queuename+" to "+speed+" bytes per second");
	}
}
